Skip to content

HYPERFLEET-538 - feat: CEL-based condition mapping engine - #329

Open
tirthct wants to merge 22 commits into
openshift-hyperfleet:mainfrom
tirthct:HYPERFLEET-538
Open

HYPERFLEET-538 - feat: CEL-based condition mapping engine#329
tirthct wants to merge 22 commits into
openshift-hyperfleet:mainfrom
tirthct:HYPERFLEET-538

Conversation

@tirthct

@tirthct tirthct commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements a CEL-based condition mapping engine that allows operators to declaratively expose adapter-specific conditions in the public API status.conditions array via YAML configuration — no code changes required.

This PR continues the work originally authored by @ldornele in #315, with review feedback from @mliptak0 addressed in the final commit.

What Changed

  • CEL evaluation engine (pkg/services/condition_mapper.go): compiles mapping rules at startup (fail-fast), evaluates on every adapter status update. Errors trigger transaction rollback for timely retry (10s vs 30min).
  • Config-driven rules (pkg/registry/conditions.go): each entity defines conditions[] with when/output CEL expressions. Reserved types (Reconciled, LastKnownReconciled, per-adapter synthesized) cannot be overridden.
  • Custom CEL functions (pkg/util/cel.go): toJson(value) for marshaling, dig(target, "dot.path") for safe nested navigation.
  • Sensitive data masking (pkg/util/mask_sensitive.go): adapter data fields matching sensitive patterns are redacted before CEL evaluation.
  • Documentation (docs/config.md): condition mapping reference moved from inline config comments to the config guide.

Review feedback addressed (from #315)

  • Removed JIRA ticket references (HYPERFLEET-538) from configs/config.yaml.example
  • Moved 34-line CEL documentation block from config example to docs/config.md
  • Removed stale comment in pkg/config/loader.go
  • Extracted buildConditionMappers() helper from NewResourceService constructor
  • Removed unexplained tags (QUAL-01, PERF-03, SEC-02, etc.) from all production and test files

Test plan

  • go vet ./pkg/util/... ./pkg/config/... passes
  • go test ./pkg/util/... passes (CEL functions, masking, naming)
  • Full test suite (go test ./...) — blocked by pre-existing mock build errors in cluster_mock.go / node_pool_mock.go (unrelated to this PR; api.Cluster / api.NodePool types undefined on this branch)
  • Verify condition mapping end-to-end with HYPERFLEET_TEST_CONDITION_MAPPING=1

Original author

@ldornele — full implementation across 18 commits. This PR supersedes #315.

@openshift-ci
openshift-ci Bot requested review from pnguyen44 and rafabene August 6, 2026 19:11
@openshift-ci

openshift-ci Bot commented Aug 6, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign jsell-rh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added configurable CEL-based condition mapping for resources.
    • Conditions can be derived from adapter statuses and resource data.
    • Added sensitive-data masking, output validation, truncation, and timestamp preservation.
    • Added configuration examples and documentation.
  • Bug Fixes

    • Improved condition recalculation when mapped inputs change.
    • Added rollback handling when condition evaluation fails.
  • Tests

    • Added comprehensive coverage for mapping, validation, masking, and error handling.

Walkthrough

The change adds CEL condition mappings to entity descriptors. Registry validation checks condition types and CEL expressions. ConditionMapper compiles and evaluates mappings against adapter statuses and resources. Sensitive fields are masked, Unknown conditions are filtered, outputs are truncated safely, and prior timestamps are preserved. Resource processing persists mapped conditions and rolls back on mapping errors. Documentation, configuration examples, dependencies, unit tests, benchmarks, and integration tests were added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Registry
  participant ResourceService
  participant Adapter
  participant ConditionMapper
  participant ResourceStore
  Registry->>ResourceService: Provide validated condition mappings
  ResourceService->>ConditionMapper: Compile mappings by resource kind
  Adapter->>ResourceService: Submit adapter statuses and data
  ResourceService->>ConditionMapper: Apply mappings with resource and prior conditions
  ConditionMapper-->>ResourceService: Return mapped conditions or error
  ResourceService->>ResourceStore: Persist mapped conditions
Loading

Suggested reviewers: pnguyen44, rafabene, kuudori

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: a CEL-based condition mapping engine.
Description check ✅ Passed The description directly explains the CEL condition mapping engine, configuration, masking, documentation, testing, and known test limitations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sec-02: Secrets In Log Output ✅ Passed Full PR scan found no logging call with token, password, credential, or secret fields/interpolations; logged fields are resource, adapter, condition, generation, and errors. CWE-532 not triggered.
No Hardcoded Secrets ✅ Passed No hardcoded secrets found: added config has no long base64 or credentials, and credential-looking literals/URLs are synthetic test fixtures or documentation samples (no CWE-798 finding).
No Weak Cryptography ✅ Passed No tracked use of crypto/md5, DES, RC4, SHA-1, ECB, custom crypto, or secret comparisons was found; JWT validation restricts signing to RS256. No CWE-327 or CWE-208 finding.
No Injection Vectors ✅ Passed The full PR diff adds no untrusted SQL, exec.Command, template.HTML, or yaml.Unmarshal sink; added fmt.Sprintf calls format errors or values only, so no CWE-89, CWE-78, CWE-79, or CWE-502 vector is...
No Privileged Containers ✅ Passed No prohibited Kubernetes or Helm settings were found. Dockerfile USER root is documented for installing make, then switches to UID 1001; runtime uses UID 65532. No CWE-250 finding.
No Pii Or Sensitive Data In Logs ✅ Passed New logs contain only resource/adapter identifiers, condition type, generation, and generic JSON errors; no PII, session IDs, request/response bodies, or credentialed hostnames are logged.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands.

@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Risk Score: 5 — risk/high

Signal Detail Points
PR size 5342 lines (>500) +2
Sensitive paths cmd/ +2
Test coverage Missing tests for: cmd/hyperfleet-api/container +1

Computed by hyperfleet-risk-scorer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (5)
pkg/services/condition_mapper.go (2)

254-262: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the condition-type length check to startup validation.

rule.conditionType comes from configuration and never changes after NewConditionMapper. Checking it on every evaluation, then failing the transaction, converts a static config defect into a permanent runtime rollback loop. pkg/registry/conditions.go already validates rules at load time. Enforce MaxConditionTypeLength there and drop the check here.

Also note the log level: this path returns an error that rolls back the transaction, so Warn understates it (HYG-02).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/condition_mapper.go` around lines 254 - 262, Move the
MaxConditionTypeLength validation from the condition-evaluation path in the
condition mapper into the startup rule validation in pkg/registry/conditions.go,
alongside the existing rule checks. Remove the per-evaluation length check,
warning, and error from the mapper so valid startup-validated rules proceed
without runtime rejection.

Source: Path instructions


443-479: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Single-entry cache degrades to pure overhead under concurrent multi-resource load.

One ConditionMapper instance serves every resource of a kind. With N concurrent resources, each Apply misses, performs the full marshal plus MaskSensitiveFields, then takes the write lock to evict the previous entry. The result is the uncached cost plus lock contention on the hot path, inside the GetForUpdate row lock.

Consider a small sharded or LRU cache keyed by resource ID, or drop the cache and keep the code simpler. The existing comment at lines 80-81 already anticipates this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/condition_mapper.go` around lines 443 - 479, Replace the
single-entry cachedResource logic in ConditionMapper.getCachedOrBuildResource
with a cache that retains multiple resources by ID, such as a small bounded LRU
or sharded cache, so concurrent resources do not continually evict one another;
alternatively remove the cache entirely if that matches the existing design
guidance. Preserve generation-based invalidation and thread-safe access, while
keeping the non-Resource fallback unchanged.
pkg/services/condition_mapper_test.go (1)

1688-1754: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a cache-invalidation test for the generation bump.

This test uses a distinct ID and generation per goroutine, so every call misses the cache. Nothing here proves the correctness claim documented at condition_mapper.go lines 72-78: that a generation bump invalidates the cached masked map. A stale hit would leak pre-PATCH spec values into mapped condition messages.

Add a sequential test that calls Apply twice with the same resource ID, mutates a spec field, bumps Generation, and asserts the mapped message reflects the new value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/condition_mapper_test.go` around lines 1688 - 1754, Add a
sequential cache-invalidation test alongside TestConditionMapper_ConcurrentApply
that uses the same resource ID, calls mapper.Apply once, mutates a spec field,
increments Generation, and calls Apply again. Assert the second mapped condition
message contains the updated spec value, proving the cached masked map is
invalidated on a generation bump.

Source: Path instructions

pkg/registry/conditions.go (1)

54-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the misleading comment and hoist the per-entity rebuild.

The comment says "Build reserved types for this specific entity". buildReservedConditionTypes iterates every entity and derives adapter types from all of them, so the set is global, not per-entity. pkg/registry/registry.go line 173 calls this function once per entity, so both the reserved set and the CEL environment are rebuilt for every entity with mappings. The cost is startup-only, but the comment misstates the contract and invites a wrong change later.

Correct the comment. Consider accepting a prebuilt reserved set and *cel.Env from the caller so Validate builds each once.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/registry/conditions.go` around lines 54 - 61, Update the validation flow
around Validate and buildReservedConditionTypes to describe the reserved types
as global across all entities, not specific to one entity. Hoist construction of
the reserved set and CEL environment to the registry caller so they are built
once, then pass the prebuilt values into each per-entity validation call while
preserving existing validation behavior.
pkg/registry/conditions_test.go (1)

228-234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the duplicated descriptor in the table cases.

Every case declares the same descriptor twice: once inside entities and once as descriptor. In all seven cases descriptor equals entities[0]. The duplication doubles the table length and lets the two copies drift, which would make a case pass for the wrong reason.

Drop the descriptor field and pass tt.entities[0] at line 498. Keep a separate field only if a case needs a descriptor that is not registered.

Also add a case for an expression that compiles but returns the wrong type, once the type check from pkg/registry/conditions.go lines 170-202 lands.

As per path instructions: "Table-driven tests with t.Run() for repeated patterns".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/registry/conditions_test.go` around lines 228 - 234, Remove the
duplicated descriptor field from the table-driven test cases in the conditions
test and update the invocation to pass tt.entities[0] instead; retain a separate
descriptor only for cases where it differs from the registered entity. Add a
t.Run() case covering an expression that compiles successfully but produces an
incorrect type, exercising the type-checking behavior in the conditions
implementation.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configs/config.yaml.example`:
- Line 124: Update the CEL examples so s.adapter uses the bare adapter
identifiers reported by adapters and declared in required_adapters: change
landing-zone-adapter to the verified reported identifier in all affected
expressions, change validation-adapter to validation in
configs/config.yaml.example, and update all four corresponding expressions in
docs/config.md. In the documentation section, state that s.adapter must equal
the value reported by the adapter. Apply the changes at
configs/config.yaml.example lines 124-124 and docs/config.md lines 370-384.
- Around line 119-137: Comment out the example `conditions: []` key so
uncommenting the following CEL condition sequence does not create duplicate YAML
values; apply the same adjustment to the `NodePool` example near its
corresponding `conditions` entry. Preserve the existing example sequence and
indentation.

In `@pkg/registry/conditions.go`:
- Around line 170-202: Update validateCELExpression to retain the checked AST
returned by env.Check, then validate checked.OutputType() before env.Program:
require cel.BoolType for when expressions and cel.StringType for output fields,
while allowing cel.DynType when the result is not statically known. Use
expectedType.IsAssignableType(outputType), with the expected type selected from
condType, and preserve the existing parse, check, and compile error handling.

In `@pkg/services/condition_mapper.go`:
- Around line 24-27: Remove the unusable env CEL variable from the CEL
environment definition and stop binding emptyEnvMap during rule evaluation, or
otherwise make Check reject any expression referencing env before startup.
Ensure env references fail during validation rather than reaching evaluateRule
and triggering rollback retries.

In `@pkg/services/resource.go`:
- Around line 600-606: Update the recompute gate in the status-change logic
around hasMapper to also compare the persisted mapped conditions’ observed
generation with resource.Generation. Trigger recomputation when those
generations differ, while preserving the existing triggerAggregation and
Conditions/Data comparisons.
- Around line 53-71: Update buildConditionMappers to return an error when
NewConditionMapper fails instead of logging and continuing with a missing
mapper. Propagate that error through NewResourceService and make startup abort,
preserving successful mapper construction and registration for valid
descriptors.

In `@pkg/util/cel.go`:
- Around line 133-149: Update digFunc’s type traversal to support arbitrary
slice kinds, including []map[string]interface{}, by using reflection in addition
to the existing map and []interface{} handling. Preserve numeric index
validation and return types.NullValue for invalid, negative, or out-of-range
indices and unsupported values.
- Around line 87-99: The comments around limitedWriter and json.Encoder.Encode
incorrectly claim that encoding allocations are bounded. Remove or correct those
claims and update the related test description to reflect that limitedWriter
only limits bytes written, unless replacing the encoder with a genuinely
incremental serializer is within scope.

In `@pkg/util/mask_sensitive_test.go`:
- Around line 13-19: Replace every RegisterTestingT(t) call in the subtests of
TestMaskSensitiveFields and the other parallel top-level tests in this file with
a per-test Gomega assertion object created via NewWithT(t), and update
assertions to use that object while preserving existing test behavior.

---

Nitpick comments:
In `@pkg/registry/conditions_test.go`:
- Around line 228-234: Remove the duplicated descriptor field from the
table-driven test cases in the conditions test and update the invocation to pass
tt.entities[0] instead; retain a separate descriptor only for cases where it
differs from the registered entity. Add a t.Run() case covering an expression
that compiles successfully but produces an incorrect type, exercising the
type-checking behavior in the conditions implementation.

In `@pkg/registry/conditions.go`:
- Around line 54-61: Update the validation flow around Validate and
buildReservedConditionTypes to describe the reserved types as global across all
entities, not specific to one entity. Hoist construction of the reserved set and
CEL environment to the registry caller so they are built once, then pass the
prebuilt values into each per-entity validation call while preserving existing
validation behavior.

In `@pkg/services/condition_mapper_test.go`:
- Around line 1688-1754: Add a sequential cache-invalidation test alongside
TestConditionMapper_ConcurrentApply that uses the same resource ID, calls
mapper.Apply once, mutates a spec field, increments Generation, and calls Apply
again. Assert the second mapped condition message contains the updated spec
value, proving the cached masked map is invalidated on a generation bump.

In `@pkg/services/condition_mapper.go`:
- Around line 254-262: Move the MaxConditionTypeLength validation from the
condition-evaluation path in the condition mapper into the startup rule
validation in pkg/registry/conditions.go, alongside the existing rule checks.
Remove the per-evaluation length check, warning, and error from the mapper so
valid startup-validated rules proceed without runtime rejection.
- Around line 443-479: Replace the single-entry cachedResource logic in
ConditionMapper.getCachedOrBuildResource with a cache that retains multiple
resources by ID, such as a small bounded LRU or sharded cache, so concurrent
resources do not continually evict one another; alternatively remove the cache
entirely if that matches the existing design guidance. Preserve generation-based
invalidation and thread-safe access, while keeping the non-Resource fallback
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: a5f5a2ce-e73b-4521-b92e-f45ecc97e5d4

📥 Commits

Reviewing files that changed from the base of the PR and between cf39733 and c49dbf9.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/go.sum
📒 Files selected for processing (20)
  • configs/config.yaml.example
  • docs/config.md
  • go.mod
  • pkg/registry/conditions.go
  • pkg/registry/conditions_test.go
  • pkg/registry/descriptor.go
  • pkg/registry/registry.go
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/condition_mapper.go
  • pkg/services/condition_mapper_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • pkg/util/cel.go
  • pkg/util/cel_test.go
  • pkg/util/mask_sensitive.go
  • pkg/util/mask_sensitive_test.go
  • pkg/util/naming.go
  • pkg/util/naming_test.go
  • test/integration/condition_mapping_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
💤 Files with no reviewable changes (1)
  • pkg/services/aggregation_test.go

Comment thread configs/config.yaml.example
Comment thread configs/config.yaml.example
Comment thread pkg/registry/conditions.go
Comment thread pkg/services/condition_mapper.go Outdated
Comment thread pkg/services/resource.go Outdated
Comment thread pkg/services/resource.go
Comment thread pkg/util/cel.go Outdated
Comment thread pkg/util/cel.go
Comment thread pkg/util/mask_sensitive_test.go
ldornele and others added 20 commits August 6, 2026 12:27
Address code review findings from PR review:
- Move condition validation from pkg/config to pkg/registry (better cohesion)
- Add blank lines between adjacent top-level function declarations
- Update config.yaml.example documentation for Unknown filtering behavior
- Format code with gofmt

Changes:
- pkg/registry/conditions.go: Moved from pkg/config (validation logic belongs with registry)
- pkg/registry/conditions_test.go: Moved from pkg/config
- configs/config.yaml.example: Clarify Unknown filtering (entire adapter status dropped)
- pkg/services/condition_mapper_test.go: Add blank lines between functions
- pkg/util/cel_test.go: Add blank lines between functions
- pkg/util/mask_sensitive_test.go: Add blank line before TestIsSensitiveKey

All tests passing. No functional changes.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Design doc (condition-mapping-design.md § Error Handling) mandates:
"If a CEL expression fails, the entire mapping operation fails and
the database transaction is rolled back."

Changes:
- Apply() signature: ([]api.ResourceCondition, error) instead of []api.ResourceCondition
- CEL evaluation errors return error instead of skip-and-continue
- resource.go propagates error as GeneralError → triggers rollback
- Test: TestProcessAdapterStatus_ConditionMapperError_TriggersRollback

Impact: CEL failures trigger 10s retry instead of 30min delay.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Without MarkForRollback, transaction commits despite error.
Aligns with other error paths (lines 234, 265, 339, 932).
Expect(err) inside goroutine calls t.Fatal from non-test goroutine,
which panics. Move assertion to main test goroutine instead.

Fixes: send err through channel, assert on res.err after receive.
…ent skip

validateFieldLengths error returned (nil, nil) - indistinguishable
from when=false skip. Propagate error for consistency with CEL
rollback-on-failure design (lines 170-177).

Note: conditionType length validated at startup, so this is
defense-in-depth (runtime path unreachable in practice).
When hasUnknown=true, adapterStatusToMapWithUnknownCheck allocated a
full 4-key map that buildStatusesList immediately discarded. Return
nil instead - caller guards with if !hasUnknown so never reads the map.

Also adds test coverage for Unknown filtering path (missing coverage
for hasUnknown=true code path).
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/services/condition_mapper_test.go (1)

1756-1780: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

testBuildActivation re-implements production logic, so the tests that use it do not test production code.

TestAdapterStatusToMapWithUnknownCheck_NilGuard and TestBuildActivation_NumericTypesConsistency assert against this copy. If buildActivationWithCache changes, for example if masking or a variable binding is added or removed, the helper keeps passing and the drift is not detected.

Call (&ConditionMapper{resourceKind: resourceKind}).buildActivationWithCache(ctx, statuses, resource) instead. The non-*api.Resource input already takes the uncached fallback path at Line 447, so no cache is involved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/condition_mapper_test.go` around lines 1756 - 1780, Remove the
test-only testBuildActivation helper and update
TestAdapterStatusToMapWithUnknownCheck_NilGuard and
TestBuildActivation_NumericTypesConsistency to call
(&ConditionMapper{resourceKind: resourceKind}).buildActivationWithCache(ctx,
statuses, resource) directly. Preserve the existing inputs and assertions so
these tests exercise the production activation-building path, including its
uncached fallback for non-*api.Resource values.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/services/condition_mapper.go`:
- Around line 253-262: Move the conditionType length validation from the runtime
evaluation path into compileRule, returning a startup configuration error when
rule.conditionType exceeds registry.MaxConditionTypeLength. Remove the
corresponding check, warning log, and error return from the evaluation logic so
valid compiled rules are not revalidated on every evaluation.

---

Nitpick comments:
In `@pkg/services/condition_mapper_test.go`:
- Around line 1756-1780: Remove the test-only testBuildActivation helper and
update TestAdapterStatusToMapWithUnknownCheck_NilGuard and
TestBuildActivation_NumericTypesConsistency to call
(&ConditionMapper{resourceKind: resourceKind}).buildActivationWithCache(ctx,
statuses, resource) directly. Preserve the existing inputs and assertions so
these tests exercise the production activation-building path, including its
uncached fallback for non-*api.Resource values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 0312380b-ab2a-41f4-b336-5464745689a5

📥 Commits

Reviewing files that changed from the base of the PR and between cf39733 and 1222d5a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/go.sum
📒 Files selected for processing (20)
  • configs/config.yaml.example
  • docs/config.md
  • go.mod
  • pkg/registry/conditions.go
  • pkg/registry/conditions_test.go
  • pkg/registry/descriptor.go
  • pkg/registry/registry.go
  • pkg/services/aggregation.go
  • pkg/services/aggregation_test.go
  • pkg/services/condition_mapper.go
  • pkg/services/condition_mapper_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • pkg/util/cel.go
  • pkg/util/cel_test.go
  • pkg/util/mask_sensitive.go
  • pkg/util/mask_sensitive_test.go
  • pkg/util/naming.go
  • pkg/util/naming_test.go
  • test/integration/condition_mapping_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
💤 Files with no reviewable changes (1)
  • pkg/services/aggregation_test.go
🚧 Files skipped from review as they are similar to previous changes (17)
  • docs/config.md
  • pkg/util/naming_test.go
  • pkg/util/cel.go
  • test/integration/condition_mapping_test.go
  • pkg/services/resource_test.go
  • pkg/registry/descriptor.go
  • pkg/services/aggregation.go
  • go.mod
  • pkg/util/mask_sensitive.go
  • pkg/util/cel_test.go
  • pkg/registry/conditions_test.go
  • pkg/util/naming.go
  • pkg/util/mask_sensitive_test.go
  • pkg/services/resource.go
  • pkg/registry/registry.go
  • pkg/registry/conditions.go
  • configs/config.yaml.example

Comment thread pkg/services/condition_mapper.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
pkg/services/resource.go (1)

591-604: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Recompute mapped conditions after resource-generation changes.

The recomputation gate compares adapter Conditions and Data, but it does not compare resource.Generation. A PATCH can change resource.spec or resource.Generation while the adapter report remains unchanged. The persisted mapped conditions then remain stale.

Include resource.Generation or the mapped conditions’ observed generation in the recomputation gate. This is the unresolved finding from the previous review.

As per path instructions, cross-layer state and persistence contracts must be traced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/resource.go` around lines 591 - 604, The recomputation gate
around triggerAggregation and hasMapper must also detect resource-generation
changes, not only adapter status Conditions and Data changes. Compare
resource.Generation with the mapped conditions’ observed generation (or the
corresponding persisted generation field) so unchanged adapter reports still
recompute after a spec-generation update, while preserving the existing
duplicate-report gating behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/hyperfleet-api/container/services.go`:
- Around line 9-19: Change ResourceService to return both the service and the
NewResourceService error instead of panicking when construction fails. Update
runServe to receive and handle that error before starting the API server,
preserving the startup failure flow without converting configuration errors into
a panic.

In `@pkg/services/resource.go`:
- Around line 44-45: Wrap the constructor error in the resource service creation
flow before returning it, adding service-level context that identifies the
failed operation while preserving the original cause for unwrapping. Replace the
bare return in the visible err-checking block and maintain the existing nil
result behavior.
- Around line 42-45: Update the test helpers calling NewResourceService to
handle its returned error instead of discarding it: assert the error is nil or
propagate it through the helper’s existing test/error mechanism. Apply this
consistently to all four helper call sites referenced in the comment so
mapper-construction failures are not masked.

---

Duplicate comments:
In `@pkg/services/resource.go`:
- Around line 591-604: The recomputation gate around triggerAggregation and
hasMapper must also detect resource-generation changes, not only adapter status
Conditions and Data changes. Compare resource.Generation with the mapped
conditions’ observed generation (or the corresponding persisted generation
field) so unchanged adapter reports still recompute after a spec-generation
update, while preserving the existing duplicate-report gating behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 8733cf04-4dd5-4502-9077-428d3c5204a0

📥 Commits

Reviewing files that changed from the base of the PR and between 1222d5a and 35ecab5.

📒 Files selected for processing (14)
  • cmd/hyperfleet-api/container/services.go
  • docs/config.md
  • go.mod
  • pkg/registry/conditions.go
  • pkg/registry/conditions_test.go
  • pkg/registry/descriptor.go
  • pkg/services/condition_mapper.go
  • pkg/services/condition_mapper_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • pkg/util/cel.go
  • pkg/util/cel_test.go
  • pkg/util/mask_sensitive_test.go
  • pkg/util/naming_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
💤 Files with no reviewable changes (3)
  • docs/config.md
  • pkg/util/cel.go
  • pkg/util/cel_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • pkg/util/naming_test.go
  • pkg/registry/conditions.go
  • go.mod
  • pkg/services/condition_mapper_test.go
  • pkg/services/condition_mapper.go
  • pkg/registry/conditions_test.go
  • pkg/services/resource_test.go

Comment on lines +9 to +19
svc, err := services.NewResourceService(
c.ResourceDao(),
c.ResourceLabelDao(),
c.AdapterStatusDao(),
c.ResourceConditionDao(),
c.GenericService(),
)
if err != nil {
panic("failed to create resource service: " + err.Error())
}
c.resourceService = svc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 '\bResourceService\s*\(\)' --glob '*.go' .
rg -n -C 8 '\b(New|Initialize|Run|Start|Serve|ListenAndServe)[A-Za-z0-9_]*\s*\(' --glob '*.go' cmd

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 50392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- services.go ---'
cat -n cmd/hyperfleet-api/container/services.go
printf '%s\n' '--- container definition and constructors ---'
rg -n -C 6 'type Container struct|func NewContainer|resourceService|NewResourceService' cmd/hyperfleet-api/container pkg
printf '%s\n' '--- serve startup sequence ---'
sed -n '40,190p' cmd/hyperfleet-api/servecmd/cmd.go
printf '%s\n' '--- relevant tests ---'
sed -n '1,100p' cmd/hyperfleet-api/container/container_test.go
printf '%s\n' '--- all ResourceService call sites, compact ---'
rg -n '\b[A-Za-z0-9_]*ResourceService\s*\(' --glob '*.go' .

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 33261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ResourceService constructor ---'
sed -n '1,115p' pkg/services/resource.go
printf '%s\n' '--- registry and CEL-related construction ---'
rg -n -C 8 'CEL|cel|condition|MustGet|NewResourceService' pkg cmd/hyperfleet-api --glob '*.go'
printf '%s\n' '--- startup call-order verifier ---'
python3 - <<'PY'
from pathlib import Path

source = Path("cmd/hyperfleet-api/servecmd/cmd.go").read_text()
resource = source.index("ctr.ResourceService()")
server = source.index("go apiServer.Start()")
print({
    "resource_service_called_before_api_server_start": resource < server,
    "resource_service_line": source[:resource].count("\n") + 1,
    "api_server_start_line": source[:server].count("\n") + 1,
})

container = Path("cmd/hyperfleet-api/container/services.go").read_text()
print({
    "constructor_error_panics": 'panic("failed to create resource service: " + err.Error())' in container,
    "resource_service_assignment_is_unsynchronized": (
        "if c.resourceService == nil" in container
        and "c.resourceService = svc" in container
        and "sync.Once" not in container
        and "sync.Mutex" not in container
    ),
})
PY

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 50392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

source = Path("cmd/hyperfleet-api/servecmd/cmd.go").read_text()
resource_pos = source.index("ctr.ResourceService()")
server_pos = source.index("go apiServer.Start()")

container = Path("cmd/hyperfleet-api/container/services.go").read_text()

print("resource_service_before_api_server_start:", resource_pos < server_pos)
print("resource_service_line:", source[:resource_pos].count("\n") + 1)
print("api_server_start_line:", source[:server_pos].count("\n") + 1)
print("constructor_error_panics:",
      'panic("failed to create resource service: " + err.Error())' in container)
print("synchronization_primitive_present:",
      any(token in container for token in ("sync.Once", "sync.Mutex", "sync.RWMutex")))
PY

printf '%s\n' '--- non-test ResourceService accessor call sites ---'
rg -n '\b[A-Za-z0-9_]*ResourceService\s*\(' --glob '*.go' --glob '!**/*_test.go' \
  cmd pkg test

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 922


Propagate NewResourceService errors instead of panicking.

runServe already calls ResourceService() before starting the API server, so invalid CEL mappings fail during startup. Change the accessor to return the constructor error and handle it in runServe; do not convert configuration errors into panic (CWE-703).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/hyperfleet-api/container/services.go` around lines 9 - 19, Change
ResourceService to return both the service and the NewResourceService error
instead of panicking when construction fails. Update runServe to receive and
handle that error before starting the API server, preserving the startup failure
flow without converting configuration errors into a panic.

Source: Path instructions

Comment thread pkg/services/resource.go
Comment on lines +42 to +45
) (ResourceService, error) {
mappers, err := buildConditionMappers(registry.All())
if err != nil {
return nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 '\bNewResourceService\s*\(' --glob '*.go' .

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 5075


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- all NewResourceService call sites ---'
rg -n -C 8 '\bNewResourceService\s*\(' --glob '*.go' .

printf '%s\n' '--- constructor and container accessor ---'
sed -n '1,90p' pkg/services/resource.go
sed -n '1,80p' cmd/hyperfleet-api/container/services.go

printf '%s\n' '--- registry and mapper construction ---'
rg -n -C 8 'buildConditionMappers|ConditionMappers|registry\.All\(' pkg/services --glob '*.go'

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 13294


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

pattern = re.compile(r'\bNewResourceService\s*\(')
for path in Path('.').rglob('*.go'):
    text = path.read_text()
    for match in pattern.finditer(text):
        line = text.count('\n', 0, match.start()) + 1
        start = text.rfind('\n', 0, match.start()) + 1
        prefix = text[start:match.start()].strip()
        print(f'{path}:{line}: {prefix}')
PY

printf '%s\n' '--- mapper error paths and registry descriptors ---'
rg -n -C 6 'func NewConditionMapper|return .*err|Conditions:|Register|registry\.All' pkg --glob '*.go'

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 50392


Handle constructor errors in test helpers. The helpers at pkg/services/resource_test.go:282, :293, :305, and :315 discard the NewResourceService error. Assert or propagate it to avoid masking mapper-construction failures (CWE-391).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/resource.go` around lines 42 - 45, Update the test helpers
calling NewResourceService to handle its returned error instead of discarding
it: assert the error is nil or propagate it through the helper’s existing
test/error mechanism. Apply this consistently to all four helper call sites
referenced in the comment so mapper-construction failures are not masked.

Source: Path instructions

Comment thread pkg/services/resource.go
Comment on lines +44 to +45
if err != nil {
return nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Wrap the constructor error before returning.

Line 45 returns err without service-level context. Return a wrapped error that identifies the failed operation.

Proposed fix
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("initialize resource service: %w", err)
 	}

As per path instructions, ERR-01 to ERR-04 require checked and wrapped errors. Bare error returns are not allowed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err != nil {
return nil, err
if err != nil {
return nil, fmt.Errorf("initialize resource service: %w", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/resource.go` around lines 44 - 45, Wrap the constructor error in
the resource service creation flow before returning it, adding service-level
context that identifies the failed operation while preserving the original cause
for unwrapping. Replace the bare return in the visible err-checking block and
maintain the existing nil result behavior.

Source: Path instructions


// Field length constraints
const (
MaxConditionTypeLength = 100

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Blocking

Category: JIRA

The JIRA AC (HYPERFLEET-538) and docs/config.md line 356 both say the type field limit is 128 characters/bytes, but this constant is set to 100. The test at conditions_test.go:583 also asserts against 100. Either the constant should be 128 to match the spec, or the doc/JIRA should be updated to reflect 100.

Suggested change
MaxConditionTypeLength = 100
MaxConditionTypeLength = 128

Comment on lines +241 to +245
if len(reasonStr) > registry.MaxConditionReasonLength {
validatedReason = truncateUTF8(reasonStr, registry.MaxConditionReasonLength)
logger.With(ctx, "resource_kind", m.resourceKind, "condition_type", rule.conditionType).
Info("Condition reason truncated to max length")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Blocking

Category: JIRA

The JIRA AC (HYPERFLEET-538) says: "reason (256 chars, skip condition)" — implying the entire condition should be omitted when reason exceeds 256 chars. But the code here truncates the reason (same as message) and still produces the condition. If the design intent changed from "skip" to "truncate", please update the JIRA AC to match. If "skip condition" is the correct behavior, this needs a code change to return nil from evaluateRule when reason exceeds 256 chars.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants